/*
 *  linux/init/main.c
 *
 *  (C) 1991  Linus Torvalds
 */

#define __LIBRARY__     // 定义该变量是为了包括定义在 unistd.h 中的内嵌汇编代码等信息
#include <unistd.h>     // 如果不是 UNIX 的标准头文件
#include <time.h>       // 时间类型头文件

/*
 * we need this inline - forking from kernel space will result
 * in NO COPY ON WRITE (!!!), until an execve is executed. This
 * is no problem, but for the stack. This is handled by not letting
 * main() use the stack at all after fork(). Thus, no function
 * calls - which means inline code for fork too, as otherwise we
 * would use the stack upon exit from 'fork()'.
 *
 * Actually only pause and fork are needed inline, so that there
 * won't be any messing with the stack from main(), but we define
 * some others too.
 */
static inline _syscall0(int,fork)           // 是unistd.h 的内嵌宏 以嵌入汇编的方式调用Linux的系统调用中断 0x80
                                            // 该中断是所有系统调用的入口。该条语句实际上是 int fork()创建进程系统调用
                                            // syscall0名称后的0表示无参数，1表示1个参数
static inline _syscall0(int,pause)          // pause（）系统调用 暂停进程的执行，知道收到一个信号
static inline _syscall1(int,setup,void *,BIOS)  // int setup 系统调用
static inline _syscall0(int,sync)           // int sync（）系统调用 更新文件系统

#include <linux/tty.h>          // tty 头文件 定义了有关 tty_io 串行通信方面的参数 常数
#include <linux/sched.h>        // 调度程序头文件 定义了任务结构 task_struct 第一个初始任务的数据 还有一些以宏
                                // 的形式定义的有关描述符参数设置和获取的嵌入式汇编函数程序
#include <linux/head.h>         // head 头文件 定义了段描述符的简单机构 和几个选择符常量
#include <asm/system.h>         // 系统头文件 以宏的形式定义了许多有关设置或修改 描述符/中断门等嵌入式汇编子程序
#include <asm/io.h>             // io头文件 以宏的嵌入汇编程序形式定义对 io 端口操作的函数

#include <stddef.h>             // 标准定义头文件 定义了 NULL offsetof（TYPE，MEMBER）
#include <stdarg.h>             // 标准参数头文件 以宏的形式定义变量参数列表
#include <unistd.h>
#include <fcntl.h>              // 文件控制头文件 用于文件及其描述符的操作控制常数符号的定义
#include <sys/types.h>          // 类型头文件 定义了基本的系统数据类型

#include <linux/fs.h>           // 文件系统头文件 定义文件表结构 file buffer_head m_inode

static char printbuf[1024];     // 静态字符串数组 用作内核显示信息的缓存

extern int vsprintf();          // 送格式化输出到一字符串中 （kernal/vsprintf.c）
extern void init(void);         // 函数原型，初始化
extern void blk_dev_init(void); // 块设别初始化子程序
extern void chr_dev_init(void); // 字符设备初始化
extern void hd_init(void);      // 硬盘初始化程序
extern void floppy_init(void);  // 软驱初始化程序
extern void mem_init(long start, long end); // 内存管理初始化
extern long rd_init(long mem_start, int length);    // 虚拟盘初始化
extern long kernel_mktime(struct tm * tm);  // 建立内核时间 s
extern long startup_time;                   // 内核启动时间 开机时间 s

/*
 * This is set up by the setup-routine at boot-time
 */
#define EXT_MEM_K (*(unsigned short *)0x90002)      // 1mb 以后的扩展内存大小
#define DRIVE_INFO (*(struct drive_info *)0x90080)  // 硬盘参数表 基址
#define ORIG_ROOT_DEV (*(unsigned short *)0x901FC)  // 根文件系统所在设备号

/*
 * Yeah, yeah, it's ugly, but I cannot find how to do this correctly
 * and this seems to work. I anybody has more info on the real-time
 * clock I'd be interested. Most of this was trial and error, and some
 * bios-listing reading. Urghh.
 */

#define CMOS_READ(addr) ({ \            // 这段宏读取CMOS实时时钟信息
outb_p(0x80|addr,0x70); \               // 0x70 是写端口号 0x80|addr 是要读取的 CMOS 内存地址
inb_p(0x71); \                          // 0x71 是读端口号
})

#define BCD_TO_BIN(val) ((val)=((val)&15) + ((val)>>4)*10)      // 将 BCD 码转换成数字

static void time_init(void)             // 该 子程序取 CMOS 时钟， 并设置开机时间-startup_time s
{
	struct tm time;

	do {
		time.tm_sec = CMOS_READ(0);
		time.tm_min = CMOS_READ(2);
		time.tm_hour = CMOS_READ(4);
		time.tm_mday = CMOS_READ(7);
		time.tm_mon = CMOS_READ(8);
		time.tm_year = CMOS_READ(9);
	} while (time.tm_sec != CMOS_READ(0));
	BCD_TO_BIN(time.tm_sec);
	BCD_TO_BIN(time.tm_min);
	BCD_TO_BIN(time.tm_hour);
	BCD_TO_BIN(time.tm_mday);
	BCD_TO_BIN(time.tm_mon);
	BCD_TO_BIN(time.tm_year);
	time.tm_mon--;              // 月份 从0开始的 所以要 -1
	startup_time = kernel_mktime(&time);
}

static long memory_end = 0;             // 机器具有的内存 字节数
static long buffer_memory_end = 0;      // 高速缓冲区的末端地址
static long main_memory_start = 0;      // 主内存（将用于分页）开始的位置

struct drive_info { char dummy[32]; } drive_info;   // 用于存放硬盘参数表信息

void main(void)		/* This really IS void, no error here. */
{			/* The startup routine assumes (well, ...) this */
/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
 	ROOT_DEV = ORIG_ROOT_DEV;               // 根设备号-ROOT_DEV 机器内存数-memory_end
 	drive_info = DRIVE_INFO;                // 高速缓存末端地址-buffer_memory_end 主内存开始地址-main_memory_start
	memory_end = (1<<20) + (EXT_MEM_K<<10); // 内存大小 = 1mb+扩展内存*1024
	memory_end &= 0xfffff000;               // 忽略不到4kb的内存数  内存对其
	if (memory_end > 16*1024*1024)          // 如果内存超过 16mb 按 16mb算
		memory_end = 16*1024*1024;
	if (memory_end > 12*1024*1024)          // 如果内存>12mb 则设置缓冲区末端=4mb
		buffer_memory_end = 4*1024*1024;
	else if (memory_end > 6*1024*1024)      // 如果内存>6mb 则设置缓冲区末端=2mb
		buffer_memory_end = 2*1024*1024;
	else
		buffer_memory_end = 1*1024*1024;    // 否则 设置缓冲区末端=1mb
	main_memory_start = buffer_memory_end;  // 主内存起始位置= 缓冲区末端
#ifdef RAMDISK                              // 如果定义了虚拟盘 则初始化虚拟盘 此时主内存将减少
	main_memory_start += rd_init(main_memory_start, RAMDISK*1024);
#endif
	mem_init(main_memory_start,memory_end); // 内存空间初始化
	trap_init();                            // 陷阱们（中断向量）初始化
	blk_dev_init();                         // 块设备初始化
	chr_dev_init();                         // 字符设备初始化
	tty_init();                             // tty 初始化
	time_init();                            // 从 CMOS 中读取设备启动时间
	sched_init();                           // 调度程序初始化
	buffer_init(buffer_memory_end);         // 缓冲管理初始化 建内存链表等
	hd_init();                              // 硬盘初始化
	floppy_init();                          // 软盘初始化
	sti();                                  // 所有初始化工作都昨晚 开启中断
	move_to_user_mode();                    // 移动到用户模式下运行
	if (!fork()) {		/* we count on this going ok */
		init();         // 这段代码在 0 号 进程 fork 出来的 1 号进程中执行
	}
/*
 *   NOTE!!   For any other task 'pause()' would mean we have to get a
 * signal to awaken, but task0 is the sole exception (see 'schedule()')
 * as task 0 gets activated at every idle moment (when no other tasks
 * can run). For task0 'pause()' just means we go check if some other
 * task can run, and if not we return here.
 */
	for(;;) pause();        // 0号进程和其他进程不同
}
// 这个函数就是个输出  输出到标准的输出通道 stdout(1)
static int printf(const char *fmt, ...)
{
	va_list args;
	int i;

	va_start(args, fmt);
	write(1,printbuf,i=vsprintf(printbuf, fmt, args));
	va_end(args);
	return i;
}

static char * argv_rc[] = { "/bin/sh", NULL };  // 调用执行程序时参数的字符串数组
static char * envp_rc[] = { "HOME=/", NULL };   // 调用执行程序时的环境字符串数组

static char * argv[] = { "-/bin/sh",NULL };     // 同上
static char * envp[] = { "HOME=/usr/root", NULL };      // 同上
// init 函数运行在任务 0 创建的子进程中 它首先对第一个要执行的程序的环境进行初始化 然后加载该程序并执行
void init(void)
{
	int pid,i;
    // 读取硬盘参数 包括分区表信息并建立虚拟盘和安装根文件系统设备
	setup((void *) &drive_info);
	(void) open("/dev/tty0",O_RDWR,0);  // 用读写访问方式打开设别 ‘dev/tty0’ 这里对应终端控制台 返回的句柄0--stdin标准输入设备
	(void) dup(0);                      // 复制句柄，产生句柄1号-stdout标准输出设备
	(void) dup(0);                      // 复制句柄，产生句柄2号-stderr标准出错输出设备
	printf("%d buffers = %d bytes buffer space\n\r",NR_BUFFERS,
		NR_BUFFERS*BLOCK_SIZE);         // 打印缓冲区块数和总字节数 每块 1024 字节
	printf("Free mem: %d bytes\n\r",memory_end-main_memory_start);  // 空闲内存字节数
	if (!(pid=fork())) {    // 下面 fork 用于创建一个子进程 对于被创建的子进程 fork 会返回0值 对于原进程 会返回子进程进程号
		close(0);           //
		if (open("/etc/rc",O_RDONLY,0))
			_exit(1);       // 如果打开文件失败， 则退出
		execve("/bin/sh",argv_rc,envp_rc);  //  装入 /bin/sh程序并执行
		_exit(2);           // 若 execve 执行失败则退出
	}
    // 下面是父进程执行的语句。 wait（）是等待子进程停止或终止，其返回值应是子进程的进程号。
    // 这三句的作用是父进程等待子进程结束 &i 是存放状态信息的位置。 如果 wait()返回值不等于子进程号 则继续等待
	if (pid>0)
		while (pid != wait(&i))
			/* nothing */;

    // 如果执行到这里，说明刚创建的子进程已停止或终止了 下面循环中首先再创建一个子进程
    // 如果出错 则显示 初始化程序创建子进程失败 的信息并继续执行。对于所创建的子进程关闭所有以前还遗留的句柄 stdin stdout stderr
    // 新创建一个会话并设置进程组号，然后重新打开 /dev/tty0作为stdin 并复制成 stdout 和 stderr
    // 再次执行系统解释程序 /bin/sh 但这次执行所选用的参数和环境数组另选了一套，然后父进程再次运行wait()等待
    // 如果子进程又停止了执行，则在标准输出上显示出错信息 子进程pid停止了运行 返回码是 i 然后继续重试
    // 一个超级大循环
	while (1) {
		if ((pid=fork())<0) {
			printf("Fork failed in init\r\n");
			continue;
		}
		if (!pid) {
			close(0);close(1);close(2);
			setsid();
			(void) open("/dev/tty0",O_RDWR,0);
			(void) dup(0);
			(void) dup(0);
			_exit(execve("/bin/sh",argv,envp));
		}
		while (1)
			if (pid == wait(&i))
				break;
		printf("\n\rchild %d died with code %04x\n\r",pid,i);
		sync();
	}
	_exit(0);	/* NOTE! _exit, not exit() */
}
